上一篇文章介紹了 SOA (Service-Oriented Architecture) 的遠大願景與淡淡哀傷的下場,而這一篇要講的是 SOA 在平行宇宙的成功版本:Microservices。
Microservices 是近幾年來軟體工程領域裡滿常聽到的熱門關鍵字之一。大概是在 SOA 出現後幾年開始崛起,跟 SOA 其實有 87% 像,所以最一開始 microservices 出現的時候,都還是被與 SOA 一同稱呼。兩者想要做的都是將一個單體式架構(Monolithic Architecture)依照功能拆成小塊,達到更好的可重用性(reusability)與靈活度(flexibility)。
SOA 與 Microservices 之間最大的差別是 SOA 一開始是以一個純然概念性的姿態出現的,而 Microservices 則是從幾個公司(Netflix, Amazon 等等)自己內部實做的架構慢慢歸納的模式,可以說前者是由上到下,後者是由下到上的演變。當初 SOA 會衰落,就是因為遇到許多理論到實作上面的困難/分歧,而 Microservices 則因為是從成功的先例開始,所以實作上比較容易取得共識,並且 Microservices 涵蓋的範圍很廣,除了技術之外,模式、原則、文化、組織特徵都包含於內,是一個發展成熟的體系。
本篇文章會從 Microservices 與 Monolithic Architecture 的比較來切入,介紹 Microservices 的特徵與實作原則,再來分析在什麼樣的情況下,使用哪種架構會更加合適。開始吧!
目前單體式架構(Monolithic Architecture)仍然是主流的軟體架構。單體式架構是什麼呢?簡單來說就是一個「一站式」的應用程式—一個請求到回應的過程中,經過的各式各樣**元件(component)**都放在同一個地方(用同一個語言、在同一個地方部署)。
Microservices 則是一種將不同元件從一個大統一的架構中拆出來的模式。比如說,原本在一個 Monolithic Application 中有用戶認證、髒話過濾、圖片壓縮等等元件,這些元件的存在形式會是插件(plugin)或是函式庫(library),而在 Microservices 的架構中,就會是一個個服務(service)。
一個引入許多 libraries 跟一個由許多不同 microservices 所組成的應用程式有什麼不一樣?第一,當你改動一個 library,哪怕只是一小部分,你都必須要重新部署整個應用程式,而如果是使用 microservices,你只要重新部署該服務就好了。相信使用 monolithic architecture 的人都有過這種覺得很麻煩的經驗:明明就只改了一行的程式碼,卻要讓整個應用程式重新跑一系列的測試部署流程......
再來,當我們使用 libraries 時它會被我們載入記憶體中,成為一個 function call。當我們使用一個 service,它則是在這個程序(process)之外的元件,必須使用 web service request 或是 remote procedure call 來溝通。
如果單純將一個 Monolithic application 模組化(modularized)呢?實務上,這會碰到幾個困難點:
這些難處只有良好的 documentation 與 discipline 才能好好預防。
那 service 的好處是什麼?因為它必須要依靠 web service request 或是 remote procedure call,這強迫我們為每個元件訂出清楚明確的界線,並且讓服務與服務之間有一致的介面。有了這個強而有力的限制我們就再也不用依賴開發者的良心發現(?)來確保架構(這一方面)的品質了。
Microservices 是一個包山包海的概念,因此一不小心就可能會讓它變得模糊而難以捉模。以下介紹一些重要的 microservices 實作原則。
這是一個 SOA 與 Microservices 很不同的點:記得上一篇講到的 ESB (Enterprise Service Bus) 嗎?ESB 讓 SOA 的架構變得非常中心化,所有的服務都與 ESB 有高度依存關係。Microservices 則是相反:它強調每個服務之間的溝通管道應該要越笨越好(知道越少越好),而所有的溝通的知識都應該在元件本身裡面。
兩個最常被 Microservices 使用的 protocol 是 HTTP (request-response with resource API) 還有 lightweight messaging。前者非常的 cache-friendly (?) ,常常被使用到的 resource 可以被輕易地快取。至於第二種方法,則常常使用像是 RabbitMQ 或是 ZeroMQ 。這些機制都只是提供一個訊息傳遞的路由器(message router),自己並不會保留資訊,而資訊都存在於 endpoint 中。
除了 conceptual model 的去中心化,Microservices 也將資料儲存去中心化。通常每個服務都會有自己的資料庫—可以是同一個資料庫結構的不同實例,也可以是完全不同的資料庫系統。
Microservices 跟傳統分工方法不一樣。原本一個團隊可能會將工程部門分成 operations, backend, UI 等等—依照「功能」區分。如果是從服務出發的思維,則每一個 service team 都會是一個獨立的,自給自足的小宇宙,會有自己的 User Interface, Persistent Storage, Project Management 等人才,是跨功能的。當我們需要改動一個元件時,我們不用「跨組」溝通,只需要找組內的人協調就好了。
雖然 microservices 聽起來很理想,但同時它也存在需要我們小心處理的眉角與挑戰。
將原本放在一起的元件拆分成一個個獨立的 service,會讓每次訊息傳遞的成本變大。這也是為什麼一個 service 的粒度通常會比 library 還要大。
另一個微服務的挑戰是如何為每個服務畫出清楚合理,並且符合軟體需要的界線。如果元件之間的關係設計不妥,那麼就只是將複雜性從內部搬到外部而已,若是服務需要重構,那麼這會比使用 in-memory library 來得複雜許多。(想想將程式碼在獨立的服務之間搬移—不是一件好玩的事吧)。
由於 microservices 的資料管理是去中心化的,一筆資料需要更新,處理與呈現可能是兩個不同的 service,於是就會出現一段資料不一致的空窗期。比如說,當你更新一筆資料時,送出後網頁自動重新整理,但是資料卻要在你再次重新整理頁面後才會被更新。
多按一次重新整理還是小事,但是當企業因為不一致的資料作出不正確的判斷/決策時,就不是一件可以被忽略的事了。
不過這不是說 monolithic architecture 就沒有 eventual consistency 的問題(尤其當快取進來玩的時候...)。只是在 microservices 中,這是一個必須更加謹慎對待的題目。
Don't even consider microservices unless you have a system that's too complex to manage as a monolith.
– Martin Fowler, Microservice Premium
這張圖很清楚完整的解釋了這一點。
(取自 https://martinfowler.com/bliki/images/microservice-verdict/productivity.png)
對大多數的專案而言,monolith 還是一個比較適合的架構。microservices 的威力只在於當專案複雜度非常高,以至於維護 monolithic architecture 變得很吃力的時候才會顯現,因為要設計出一個能夠運作良好、職責分明的 microservices 架構,需要投注的心力不容小覷。
儘管如此,我們仍然要記得不管是不是 microservices,模組化都是一個我們可以謹記在心的目標。只要有良好的模組化,就算是 monolithic architecture,還是能夠享受高維護性與彈性的好處。
作者:Jenny